#pragma once
#ifndef OBR_INSTANCE_H
#define OBR_INSTANCE_H

#include "ASNode.h"
#include "Configurable.h"
#include "Light.h"
#include "Material.h"
#include "Mesh.h"
#include "SharedDS/DScommon.h"
#include "VirtualProgramManager.h"
#include "common.h"
#include "helpers/Helpers.h"

namespace OBR
{
class Instance : public ASNode
{
public:
    Instance();
    virtual ~Instance();

    /**
     * @brief link children using child data
     *
     * @param materials
     * @param mediums
     * @param meshes
     * @param instances
     */
    virtual void linkChild(VirtualProgramManager materials, VirtualProgramManager mediums,
                           VirtualProgramManager meshes, VirtualProgramManager lights,
                           std::vector<std::shared_ptr<Instance>> instances) = 0;
};

class Scene;
class StaticInstance;

/**
 * @brief represent instance created with a packed file (e.g. obj and fbx)
 *
 */
struct StaticInstanceBuildData
{
    std::vector<std::shared_ptr<Material>> materials;
    std::vector<std::shared_ptr<Mesh>> meshes;
    int medid = -1;
};

class StaticInstance : public Instance, public Configurable<StaticInstance>
{
    struct InstanceChildData
    {
        int matid = -1;
        int lightid = -1;
        int medid[2] = {-1, -1};
        ASNodeType type;
        uint asid;
    };
    /* -------------------------------- data -------------------------------- */
public:
    // stores all hit data, one per instance with material
    static std::vector<HitData> hit_data;
    // one for each hit data
    static std::vector<std::shared_ptr<Mesh>> hit_meshes;
    // gas offsets for each hit data
    static std::vector<size_t> hit_gas_offset;

    // current position in hit_data array, -1 for N/A, created on AS build
    std::vector<size_t> hit_ids;
    // instances belongs to this IAS
    std::vector<InstanceChildData> child_data;
    std::vector<std::shared_ptr<ASNode>> children;
    std::vector<std::shared_ptr<AreaLight>> surface_lights;
    // one for each instance children
    std::vector<Matrix4x4f> transforms;

private:
    CudaBuffer d_instances;
    CudaBuffer ias_output_buffer;
    OptixAabb aabb;
    StaticInstanceBuildData build_data = {};
    /* --------------------------------- methods -------------------------------- */
public:
    StaticInstance();
    ~StaticInstance();

    /**
     * @brief link children using child data
     *
     * @param materials
     * @param mediums
     * @param meshes
     * @param instances
     */
    void linkChild(VirtualProgramManager materials, VirtualProgramManager mediums,
                   VirtualProgramManager meshes, VirtualProgramManager lights,
                   std::vector<std::shared_ptr<Instance>> instances) override;

    /**
     * @brief recursively build AccelStructure
     *
     * @param ctx
     * @param stream
     */
    void buildAccelStructure(OptixDeviceContext &ctx, CUstream &stream) override;

    /**
     * @brief set local to world transform to given parent value and pass down to none GAS children
     *
     * @param l2w
     */
    void updateLocalToWorld(Matrix4x4f l2w) override;

protected:
    /**
     * @brief build from given json
     *
     * @param config
     */
    void _buildFromConfig(const Json::Value &config) override;

public:
    void postBuild(Scene &sc) override;
};

} // namespace OBR

#endif
